﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BeanboxSoftware.BeanMap;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;

namespace TurtleLib.Simulation
{
  public class World
  {
    internal List<Entity> _entities;
    protected WorldUx _mainForm;

    public World()
    {
      _entities = new List<Entity>();
      _mainForm = new WorldUx(this);
    }

    internal Entity At(Point3D loc)
    {
      return _entities.FirstOrDefault(e =>
        e.Location.X == loc.X &&
        e.Location.Y == loc.Y &&
        e.Location.Z == loc.Z);
    }

    public bool AddEntity(object obj, Point3D origin, Face facingSide = Face.South, bool initiallyExpanded = false)
    {
      var existing = At(origin);

      if (existing != null)
        return false;

      var newE = new Entity();
      newE.Target = obj;
      newE.Location = origin;
      newE.Panel = _mainForm.GetSubscriberHandler(obj.GetType())(obj);
      newE.Panel.Expanded = initiallyExpanded;
      newE.Face = facingSide;

      if (newE.Panel == null) // no UI support
        throw new NotSupportedException("No handler has been defined for: " + obj.GetType());

      _entities.Add(newE);

      return true;
    }

    public void AttachModem(ComputerCraft.Computer computer, ComputerCraft.Side side)
    {
      throw new NotImplementedException();
    }

    public void SetBootComputerScript(ComputerCraft.Computer computer, ComputerScript script)
    {
      var caccess = new ComputerCraft.Computer.ComputerAccess(computer);
      caccess.SetBootScript(script);
    }

    public void SetBootTurtleScript(ComputerCraft.Turtle turtle, TurtleScript script)
    {
      var taccess = new ComputerCraft.Turtle.TurtleAccess(turtle);
      taccess.SetBootScript(script);
    }

    public void Run()
    {
      JoinMonitors();

      var task = new Task(() =>
      {
        _mainForm.WaitUntilReady();
        StartComputers();
      });

      task.Start();
      _mainForm.Run();
    }

    private void JoinMonitors()
    {
      var monQuery = _entities.Where(e => e.Target is ComputerCraft.Monitor);
      var monitors = Map<Face, Point3D, Tuple<IPanel, ComputerCraft.Monitor>>.From(
        monQuery,
        ent => ent.Face,
        ent => new Point3D(ent.Location.X, ent.Location.Y, ent.Location.Z),
        ent => Tuple.Create(ent.Panel, ent.Target as ComputerCraft.Monitor));

      var util = new TurtleLib.ComputerCraft.PeripheralUtility();

      while (monitors.Count > 0)
      {
        var firstSet = monitors.KeyValues.First();

        var face = firstSet.Key.Item1;
        var loc = firstSet.Key.Item2;
        var panel = firstSet.Value.Item1;
        var monitor = firstSet.Value.Item2;
        var commonFace = monitors[face];

        var utilAccess = new TurtleLib.ComputerCraft.PeripheralUtility.PeripheralUtilityAccess(util);
        utilAccess.WorldFaceRequestCallback = () => face;
        utilAccess.WorldObjectLookupCallback = _ => commonFace.KeyValues.Where(cf => cf.Key == _).Select(cf => cf.Value.Item2).FirstOrDefault();

        Func<TurtleLib.ComputerCraft.Monitor, TurtleLib.ComputerCraft.Side, List<TurtleLib.ComputerCraft.Monitor>> getLine = null;

        getLine = (m, s) =>
        {
          var mloc = GetLocation(m);
          utilAccess.WorldLocationRequestCallback = () => mloc;
          var next = util.wrap(s) as ComputerCraft.Monitor;

          if (next == null || next.GetType() != monitor.GetType()) // last
            return new List<TurtleLib.ComputerCraft.Monitor>();

          var lower = getLine(next, s);
          lower.Add(next);
          return lower;
        };

        var vertical = getLine(monitor, ComputerCraft.Side.Top);
        vertical.AddRange(getLine(monitor, ComputerCraft.Side.Bottom));
        vertical.Add(monitor);
        int height = vertical.Count;

        var right = vertical.Select(v => getLine(v, ComputerCraft.Side.Right)).ToList();
        var left = vertical.Select(v => getLine(v, ComputerCraft.Side.Left)).ToList();

        int minRight = right.Min(r => r.Count);
        int minLeft = left.Min(r => r.Count);

        var adjustedRight = right.SelectMany(r => r.Take(minRight));
        var adjustedLeft = left.SelectMany(r => r.Take(minLeft));

        var full = vertical;
        full.AddRange(adjustedRight);
        full.AddRange(adjustedLeft);

        JoinMonitors(full, new Size(minLeft + minRight, height));
        full.ForEach(m => monitors[face].Remove(GetLocation(m)));
      }
    }

    private void JoinMonitors(List<ComputerCraft.Monitor> monitorGroup, Size size)
    {
      var ruler = monitorGroup.First();

      TurtleLib.ComputerCraft.Monitor.MonitorAccess maccess;

      Action<TurtleLib.ComputerCraft.Monitor, TurtleLib.ComputerCraft.Monitor> setHost = (c, h) =>
      {
        maccess = new TurtleLib.ComputerCraft.Monitor.MonitorAccess(c);
        maccess.SetHost(h);
      };

      var topEnt = GetEntity(ruler);
      _mainForm.Remove(topEnt.Panel);

      bool expanded = false;

      foreach (var m in monitorGroup.Skip(1))
      {
        setHost(m, ruler);
        var ent = GetEntity(m);
        _mainForm.Remove(ent.Panel);
        expanded |= ent.Panel.Expanded;
        ent.Panel = null;
      }

      maccess = new TurtleLib.ComputerCraft.Monitor.MonitorAccess(ruler);
      maccess.SetHost(null);
      var singleSize = ruler.getSize();
      maccess.Resize(size.Width * singleSize.Item1, size.Height * singleSize.Item2);

      expanded |= topEnt.Panel.Expanded;
      topEnt.Panel = _mainForm.GetSubscriberHandler(ruler.GetType())(ruler);
      topEnt.Panel.Expanded = expanded;
    }

    private void StartComputers()
    {
      foreach (var ent in _entities.Where(e => e.Target is ComputerCraft.Computer))
      {
        var computer = ent.Target as ComputerCraft.Computer;
        if (computer != null)
        {
          new Task(() =>
          {
            computer.turnOn();
          }).Start();
        }
      }
    }

    public IPanel GetUx(object target)
    {
      return _entities.Where(e => e.Target == target).Select(e => e.Panel).FirstOrDefault();
    }

    internal Entity GetEntity(object obj)
    {
      return _entities.FirstOrDefault(e => e.Target == obj);
    }

    public Point3D GetLocation(object obj)
    {
      var e = GetEntity(obj);
      if (e == null)
        return null;

      return e.Location;
    }

    public Face GetFace(object obj)
    {
      var e = GetEntity(obj);
      if (e == null)
        return Face.Invalid;

      return e.Face;
    }

    public object GetObject(Point3D pos)
    {
      return _entities.Where(e => e.Location == pos).Select(e => e.Target).FirstOrDefault();
    }
  }
}
